package animator.phantom.controller;

/*
    Copyright Janne Liljeblad

    This file is part of Phantom2D.

    Phantom2D is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Phantom2D is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Phantom2D.  If not, see <http://www.gnu.org/licenses/>.
*/

import animator.phantom.gui.BinsAreaPanel;
import animator.phantom.renderer.parent.AbstractParentMover;
import animator.phantom.renderer.param.*;
import javax.swing.JOptionPane;
import animator.phantom.renderer.*;
import animator.phantom.renderer.param.*;
import animator.phantom.gui.*;
import animator.phantom.gui.modals.*;
import animator.phantom.exec.*;
import java.awt.event.*;
import java.awt.*;
import javax.swing.*;
import java.util.*;
import java.io.*;

//--- Complex user initiated actions that are not in menu.
public class UserActions
{
	private static FileCountPoll fileCountPoll = null;
	private static Thread importDialogThread = null;
	private static JDialog importDialog;
	private static JLabel frameCountDisplay = null;
	private static NativeCommandRunner comRunner = null;
	private static String[] loopOptions = { "no looping","loop","ping-pong" };

	public static void addSingleFileSources( BinsAreaPanel bPanel )
	{
		try
		{
			//--- Get user selected file(s).
			File[] addFiles = GUIUtils.addFiles( GUIComponents.getAnimatorFrame(), "Select files" );
			if( addFiles == null ) return;

			//--- Create new FileSources.
			Vector<FileSource> addFileSources = new Vector<FileSource>();
			Vector<File> movies = new Vector<File>();
			for( int i = 0; i < addFiles.length; i++ )
			{
				String wrongChar = AppUtils.testStringForSequences(	
									AppUtils.forbiddenFileNameChars,
									addFiles[ i ].getName() );
				if( wrongChar != null )
				{
					displayWrongFileChars( addFiles[ i ].getName(), wrongChar );
					continue;
				}

				String ext = AppUtils.getExtension( addFiles[ i ] );
				if( AppUtils.isMovieExtension( ext ) )
				{
					movies.add( addFiles[ i ] ); 
				}
				else
				{
					if( ext.equals( "svg" ) )
						addFileSources.add( new SVGSource( addFiles[ i ] ) );
					else 
						addFileSources.add( new FileSingleImageSource( addFiles[ i ] ) );
				}
			}

			if( movies.size() > 0 )
				importMovies( movies, addFileSources );

			//--- Add them to project data. Gives id's to filesources.
			ProjectController.addFileSourcesToProject( addFileSources );
			ProjectController.addFileSourceVectorToBin( addFileSources, bPanel.currentBin() );

			//--- Get image sizes and add GUI components to panels 
			for( FileSource addFS : addFileSources )
			{
				addFS.firstLoadData();
				bPanel.currentSelectPanel().addFileSource( addFS );
				addFS.clearData();
				bPanel.currentSelectPanel().reInitSelectPanel();
				bPanel.updateGUI();
			}

			//--- MemoryManager needs to update cache.
			MemoryManager.fileSourcesAdded();
		}
		catch( Exception e )
		{
					String[] buttons = {"Ok"};
					String[] bLines = { "File import failed" };
					String[] tLines = {     "Phantom2D is unable to complete file import.",
								"It may be that you are trying to import corrupt files or", 
								"files of a type that is not supported by Phantom2D." };
					DialogUtils.showTwoTextStyleDialog( JOptionPane.WARNING_MESSAGE, null, buttons, bLines, tLines );
		}
		 
	}

	public static void displayWrongFileChars( String fileName, String wrongChar )
	{
		String[] buttons = {"Ok"};
		String[] bLines = { "File "+ fileName + " contained forbidden character \"" + wrongChar + "\"" };
		String[] tLines = {"Some operating systems or programs may not work with it.",
					"Remove forbidden character from file name and try again." };
		DialogUtils.showTwoTextStyleDialog( JOptionPane.WARNING_MESSAGE, null, buttons, bLines, tLines );
	}

	public static void addFileSequenceSource( BinsAreaPanel bPanel )
	{
		try
		{
			//--- Get user selected file(s).
			File addFile = GUIUtils.addISingleImageFile( GUIComponents.getAnimatorFrame(), "Select first frame of sequence" );
			if( addFile == null ) return;

			String wrongChar = AppUtils.testStringForSequences(	
									AppUtils.forbiddenFileNameChars,
									addFile.getName() );
			if( wrongChar != null )
			{
				String[] buttons = {"Ok"};
				String[] bLines = { "File "+ addFile.getName() + " contained forbidden character \"" + wrongChar + "\"" };
				String[] tLines = {"Some operating systems or programs may not work with it.",
							"Remove forbidden character from file name and try again." };
				DialogUtils.showTwoTextStyleDialog( JOptionPane.WARNING_MESSAGE, null, buttons, bLines, tLines );
			}
			FileSource addFS = new FileSequenceSource( addFile );
			Vector<FileSource> fsVec = new Vector<FileSource>();
			fsVec.add( addFS );

			//--- Add them to project data. Gives id's to filesources.
			ProjectController.addFileSourcesToProject( fsVec );
			ProjectController.addFileSourceVectorToBin( fsVec, bPanel.currentBin() );

			addFS.firstLoadData();
			bPanel.currentSelectPanel().addFileSource( addFS );
			addFS.clearData();
			bPanel.currentSelectPanel().reInitSelectPanel();
			bPanel.updateGUI();
			
			//--- MemoryManager needs to update cache.
			MemoryManager.fileSourcesAdded();
		}
		catch( Exception e )
		{
					String[] buttons = {"Ok"};
					String[] bLines = { "File import failed" };
					String[] tLines = {     "Phantom2D is unable to complete file import.",
								"It may be that you are trying to import corrupt files or", 
								"files of a type that is not supported by Phantom2D." };
					DialogUtils.showTwoTextStyleDialog( JOptionPane.WARNING_MESSAGE, null, buttons, bLines, tLines );
		}
		 
	}

	public static void deleteFileSources( BinsAreaPanel bPanel )
	{
		Vector<FileSource> selected = bPanel.currentSelectPanel().getSelected();
		String fsString = null;
		if( selected.size() == 0 ) return;
		else if( selected.size() == 1 ) fsString = " file source";
		else fsString = " file sources";

		Vector<RenderNode> nodesWithFs = new Vector<RenderNode>();
		for( FileSource fs : selected )
			nodesWithFs.addAll( FlowController.getNodesWithFileSource( fs ) );
		
		int answer;
		String[] options = { "Cancel","Ok" };
		if( nodesWithFs.size() == 0 ) 
			answer = DialogUtils.showTwoTextStyleDialog( 	JOptionPane.WARNING_MESSAGE, 
									"Confirm delete", 
									options,
									"Confirm delete", 
									"Delete " + selected.size() + fsString + " from bin." );
		else 
			answer = DialogUtils.showTwoTextStyleDialog(
						JOptionPane.WARNING_MESSAGE,
						"Confirm delete", 
						options,
						"Delete file sources and nodes",
						"There are nodes in the render flow that use deleted file sources." );

		if( answer == 0 || answer == JOptionPane.CLOSED_OPTION  ) return;

		ProjectController.deleteFileSourceVectorFromBin( selected, bPanel.currentBin() );
		ProjectController.deleteFileSourceVector( selected );
		MemoryManager.deleteFromViewCache( selected ); 
		FlowController.deleteVector( nodesWithFs );
	
		bPanel.currentSelectPanel().deleteSelected();
		bPanel.updateGUI();
	}

	//--- GUI for setting animation properties for iops.
	public static void manageAnimationSettings( ImageOperation iop )
	{
		Vector <ImageOperation> parentIops = FlowController.getAnimatebleIops();
		parentIops.remove( iop );
		int pselindex = 0;
		int typeselindex = 0;
		//---- ?
		if( iop.parentNodeID != -1 ) 
		{
			ImageOperation piop = ( FlowController.getNode( iop.parentNodeID )).getImageOperation();
			pselindex = parentIops.indexOf( piop ) + 1;
			typeselindex = iop.parentMoverType;
		}

		String[] options = new String[parentIops.size() + 1];
		options[ 0 ] = "none";
		for( int i = 1; i < parentIops.size() + 1; i++ )
			options[ i ] = parentIops.elementAt( i - 1 ).getName();

		MComboBox parents = new MComboBox( "Select movement parent", 75, options );
		MComboBox actions = new MComboBox( "Child follows", 75, AbstractParentMover.types );
		parents.setSelectedIndex( pselindex );
		actions.setSelectedIndex( typeselindex );
	
		MInputArea area = new MInputArea( "Parent Settings" );
		area.add( parents );
		area.add( actions );

		//String[] loopOptions = { "no looping","loop","ping-pong" };
		MComboBox looping = new MComboBox( "Select looping mode", 75, loopOptions );
		looping.setSelectedIndex( iop.getLooping() );

		MInputArea larea = new MInputArea( "Looping" );
		larea.add( looping );
 
		MInputPanel panel = new MInputPanel( "Animation Properties" );
		panel.add( area );
		panel.add( larea );

		int retVal = DialogUtils.showMultiInput( panel );

		int p = parents.getSelectedIndex();
		int ac = actions.getSelectedIndex();
		if( p == 0 ) iop.setParentMover( -1, -1, null );
		else 
		{
			ImageOperation parentIOP = parentIops.elementAt( p - 1 );
			if( isCyclicParenting( iop, parentIOP ) )
			{
				String[] tLines = {"Node/parent relationships can't form cycles.", "Parent set edit cancelled." };
				DialogUtils.showTwoStyleInfo( "Cyclic parenting detected", tLines, DialogUtils.WARNING_MESSAGE );
			}
			else
			{
				RenderNode node = FlowController.getNode( parentIOP );
				iop.setParentMover( ac, node.getID(), parentIOP  );
			}
		}

		iop.setLooping( looping.getSelectedIndex() );
		ParamEditController.reBuildEditFrame();
		UpdateController.valueChangeUpdate();
	}
	//--- Returns true if parenting list started from child includes cycle
	private static boolean isCyclicParenting( ImageOperation child, ImageOperation parent )
	{
		Vector<ImageOperation> list = new Vector<ImageOperation>();
		list.add( child );
		if( list.contains( parent ) )
			return true;
		list.add( parent );
		ImageOperation nextParent = parent.getParent();
		while( nextParent != null )
		{
			if( list.contains( nextParent ) )
				return true;
			list.add( nextParent );
			nextParent = nextParent.getParent();
		}
		return false;
	}

	//--- GUI for settings looping value for iops that dont have parents
	public static void manageLoopSettings( ImageOperation iop )
	{
		//String[] loopOptions = { "no looping","loop","ping-pong" };
		MComboBox looping = new MComboBox( "Select looping mode", 75, loopOptions );
		looping.setSelectedIndex( iop.getLooping() );

		MInputArea larea = new MInputArea( "Looping" );
		larea.add( looping );
 
		MInputPanel panel = new MInputPanel( "Animation Properties" );
		panel.add( larea );

		int retVal = DialogUtils.showMultiInput( panel );

		iop.setLooping( looping.getSelectedIndex() );
		ParamEditController.reBuildEditFrame();
		UpdateController.valueChangeUpdate();
	}
	//--- 
	public static void importMovies( Vector<File> movies, Vector<FileSource> addFileSources  )
	{
		importMovies( movies, addFileSources, false );
	}

	//--- 
	public static void importMovies( Vector<File> movies, Vector<FileSource> addFileSources, boolean isReplaceImport )
	{
		if( CodecController.currentDecoder() == null )
		{
			String[] buttons = {"Ok"};
			String[] bLines = { "Decoder program not available in the system" };
			String[] tLines = {"Video files are imported as numbered file sequences that are created by",
				"a third party decoder program via terminal command.",
				"   ",
				"Suitable decoder program must be installed before movie clips can be imported",
				"   ",
				"Select Help->Codec Info for more information."};
			DialogUtils.showTwoTextStyleDialog( JOptionPane.WARNING_MESSAGE, null, buttons, bLines, tLines );
			return;
		}

		if( EditorPersistance.getImportFolder() == null ) 
		{
			String[] buttons = {"Ok"};
			String[] bLines = { "Select import directory" };
			String[] tLines = {"Video files are imported as numbered file sequences",
				"that are placed in a user defined import directory.",
				"Press OK to select import directory."};
			DialogUtils.showTwoTextStyleDialog( JOptionPane.WARNING_MESSAGE, null, buttons, bLines, tLines );
			setImportSettings();
		}

		if( EditorPersistance.getImportFolder() == null ) 
		{
			String[] lines = { "Import folder was not set.", "Movie file import cancelled."};
			DialogUtils.showTwoStyleInfo( "Movie Import Info", lines, DialogUtils.WARNING_MESSAGE );
			return;
		}

		String importDirPath = EditorPersistance.getImportFolder().getAbsolutePath();

		Vector<MovieSource> addSources = new Vector<MovieSource>();

		try
		{
			for( int i = 0; i < movies.size(); i++ )
			{
				NativeCommandRunner runner = new NativeCommandRunner();

				File movie = movies.elementAt( i );
				String clipDirPath = importDirPath + "/" + AppUtils.getMD5String( movie.getAbsolutePath() );
				File newDir = new File( clipDirPath );
				System.out.println( "newDir:" + newDir.getAbsolutePath() );
				if( newDir.exists() )
				{
					String[] options = {"Use existing frames","Delete and rewrite frames"};
					String[] bLines = {"Clip already imported"};
					String[] tLines = {"This clip or a clip with exactly the same name has already been imported.","Do you want to use the existing frames or write over them?"};
					int answer = DialogUtils.showTwoTextStyleDialog( DialogUtils.WARNING_MESSAGE, "Import info", options, bLines, tLines );
					if( answer == 0 )
					{
						addSources.add( new MovieSource( newDir, movie ) );
						continue;
					}
					else
					{
						File[] frames = newDir.listFiles();
						for( int j = 0; j < frames.length; j++ )
						{
							File delFile = frames[ j ];
							if( MovieSource.isSequenceFile( delFile ) ||
							    delFile.isDirectory() )
							{
								delFile.delete();
							}
							else
							{
								String[] lines = {  	"Tried to delete file that was not a image sequence file when deleting files in folder:",
											newDir.getAbsolutePath(),
											"File that was not a sequence file was:",
											delFile.getAbsolutePath(),
											"Check IMMEDIATELY that current import directory does not contain any important files!",
											"Movie file import cancelled."};
								DialogUtils.showTwoStyleInfo( "IMPORT ERROR!", lines, DialogUtils.WARNING_MESSAGE );
								continue;
							}
						}						
					}
					
				}
				else
				{
					newDir.mkdir();
				}
				CodecController.cDecodeMovie = movie.getAbsolutePath();
				launchImportDialogAndPoll( newDir, movie.getName() );
				fileCountPoll = new FileCountPoll( newDir, 300 );
				fileCountPoll.start();
				addSources.add( new MovieSource( newDir, movie ) );

				String command = CodecController.currentDecoder().getDecodeCommand();
				NativeCommandExec importCommand = new NativeCommandExec( command, new File( clipDirPath ) );
				runner.addCommand( importCommand );
				comRunner = runner;
				runner.runCommands();//blocks
				
				quitClipLoad();
			}
		}
		catch( Exception e )
		{
			System.out.println("Clip import ERROR! " + e.getMessage() );
			return;
		}

		for( MovieSource ms : addSources )
		{
			//--- Movie source is inited if this is not replace import
			if( !isReplaceImport )
				ms.initMovieSource();

			addFileSources.add( ms );
		}

	}

	//---- This displays number of written frames when importing clips
	public static void frameCountEvent( int frameCount )
	{
		if( frameCountDisplay == null ) return; 
		frameCountDisplay.setText( Integer.toString( frameCount ) );
	}

	private static void launchImportDialogAndPoll( File file, String movieName )
	{
		final String name = movieName;
		importDialogThread = 
			new Thread()
			{
				public void run()
				{
					JPanel p = new JPanel();
					p.setLayout( new BoxLayout( p, BoxLayout.Y_AXIS) );

					JLabel topRow = new JLabel( "Writing frames for " + name );
					topRow.setFont( GUIResources.BASIC_FONT_12 );
					JPanel tp = new JPanel();
					tp.add( topRow );
					p.add( tp );

					frameCountDisplay = new JLabel( "0" );
					frameCountDisplay.setFont( GUIResources.BASIC_FONT_12 );
					JPanel bp = new JPanel();
					bp.add( frameCountDisplay );
					p.add( bp );

					JOptionPane importDialogPane = DialogUtils.getImportClipDialog( p );
					importDialog = importDialogPane.createDialog( GUIComponents.getAnimatorFrame(), "Clip import" );
					importDialog.setVisible( true );
					Object selectedValue = importDialogPane.getValue();
					quitClipLoad();
				}
			};

		importDialogThread.start();
	}

	private static void quitClipLoad()
	{
		if( comRunner != null ) comRunner.stopCommand( true );
		if( importDialog != null ) importDialog.dispose();
		importDialog = null;
		if( fileCountPoll != null ) fileCountPoll.stop();
		fileCountPoll = null;
		frameCountDisplay = null;
		importDialogThread = null;
		comRunner = null;
	}

	public static void setImportSettings()
	{
		MFileSelect tfs = new MFileSelect( "Import Folder", "Select Import Folder", 25, null, null );
		tfs.setType( JFileChooser.DIRECTORIES_ONLY );

		String[] prgOps = {"not available"};
		Vector<String> decoders = CodecController.getDecoders();
		if( CodecController.currentDecoder() != null )
		{
			prgOps = new String[ decoders.size() ];
			for( int i = 0; i < decoders.size(); i++ )
				prgOps[ i ] = decoders.elementAt( i );
		}

		final MComboBox decodersSelect = new MComboBox( "Decoder program", 75, prgOps );
		if( CodecController.currentDecoder() != null )
			decodersSelect.setSelectedIndex( decoders.indexOf( CodecController.currentDecoder() ) );
		else
			decodersSelect.setSelectedIndex( 0 );

		String[] formats = {"not available"};
		if( CodecController.currentDecoder() != null )
			formats = CodecController.currentDecoder().getDecodeFrameFormats();

		final MComboBox frameFormats = new MComboBox( "Import frame format", 20, formats );
		if( CodecController.currentDecoder() != null )
			frameFormats.setSelectedIndex( MenuActions.getIndex( formats, CodecController.cDecodeFrameFormat ));
		else
			frameFormats.setSelectedIndex( 0 );

		MInputArea tArea = new MInputArea( "Import Settings" );
		tArea.add( decodersSelect );
		tArea.add( frameFormats );
		tArea.add( tfs );

		final MInputPanel iPanel = new MInputPanel( "Import Settins" );
		iPanel.add( tArea );

		decodersSelect.addActionListener
		(
			new MActionListener() 
			{
				public void actionPerformed(ActionEvent e)
				{
					if( CodecController.currentDecoder() == null )
						return;
					CodecController.setCurrentDecoder( (String) decodersSelect.getSelectedItem() );
					frameFormats.setOptions( CodecController.currentDecoder().getDecodeFrameFormats() );
				}
			}
		);

		//--- Display dialog.
		int retVal = DialogUtils.showMultiInput( iPanel );

		//--- After user inter action.
		if( retVal != DialogUtils.OK_OPTION ) return;

		File targetFolder = tfs.getSelectedFile();
		if( targetFolder != null )
		{
			EditorPersistance.setImportFolder( targetFolder );
			EditorPersistance.write();
		}

		CodecController.cDecodeFrameFormat = (String) frameFormats.getSelectedItem();
	}

	public static void displayKeySCInstructions()
	{
		String text = AppUtils.getTextFileAsString( GUIResources.keyShortcutInfo );
		DialogUtils.showTextAreaOKDialog( "Keyboard shortcuts instructions", text, 600, 600 );
	}

	public static void displayFirstRunInfo()
	{
		String [] options = {"Ok"};
		JPanel pane = new JPanel();
		pane.setLayout( new BoxLayout( pane, BoxLayout.Y_AXIS ));

		JLabel phLogo = new JLabel( GUIResources.getIcon( GUIResources.phantomLogo ) );
		JLabel welcome = new JLabel( "Welcome to Phantom2D" );
		welcome.setFont( GUIResources.BOLD_FONT_14 );
		pane.add( phLogo );
		pane.add( Box.createRigidArea( new Dimension(0,8) ) );
		pane.add( welcome );
		pane.add(  Box.createRigidArea( new Dimension(0,28) ) );

		JLabel encProgs = new JLabel( "Third party encoder programs detected to be available in the system:" );
		encProgs.setFont( GUIResources.BASIC_FONT_12);
		pane.add( encProgs );
		pane.add(  Box.createRigidArea( new Dimension(0,3) ) );
		for( String prog : CodecController.availableProgs )
		{
			pane.add( new JLabel( prog + " - found" ) );
		}
		for( String prog : CodecController.notAvailableProgs )
		{
			pane.add( new JLabel( prog + " - not found" ) );
		}

		pane.add(  Box.createRigidArea( new Dimension(0,18) ) );

		String encFunc = "Encoding rendered movie clips functionality is ";
		JLabel encFuncLabel = null;
		if( CodecController.currentEncoder() == null )
			encFuncLabel = new JLabel( encFunc + " NOT AVAILABLE." );
		else
			encFuncLabel = new JLabel( encFunc + " AVAILABLE." );
		encFuncLabel.setFont( GUIResources.BASIC_FONT_12);
		pane.add( encFuncLabel );

		pane.add(  Box.createRigidArea( new Dimension(0,2) ) );

		String decFunc = "Importing movie clips functionality is ";
		JLabel decFuncLabel = null;
		if( CodecController.currentDecoder() == null )
			decFuncLabel = new JLabel( decFunc + " NOT AVAILABLE." );
		else
			decFuncLabel = new JLabel( decFunc + " AVAILABLE." );
		decFuncLabel.setFont( GUIResources.BASIC_FONT_12);
		pane.add( decFuncLabel );

		pane.add( Box.createRigidArea( new Dimension(0,18) ) );

		JLabel infoLabel = new JLabel( "Phantom2D uses encoder programs to do clip encoding and decoding" );
		infoLabel.setFont( GUIResources.BASIC_FONT_12);
		JButton infoButton = new JButton( "More Info On Encoders & Installing Them" );
		infoButton.setFont( GUIResources.BASIC_FONT_12);
		infoButton.addActionListener
		(
			new ActionListener()
			{
				public void actionPerformed(ActionEvent event)
				{
					MenuActions.displayCodecInfo();
				}
			}
		);
		pane.add( infoLabel );
		pane.add( Box.createRigidArea( new Dimension(0,4) ) );
		pane.add( infoButton );

		PHDialog dialog = new PHDialog(GUIComponents.animatorFrame, "First run", options, pane, PHDialog.PLAIN_MESSAGE );      
		dialog.setVisible( true );//this blocks
	}

	public static void displayNoEncoderInfo()
	{
		String[] buttons = {"Ok"};
		String[] bLines = { "Encoder program/s not available in the system" };
		String[] tLines = {"Movie clips are encoded from rendered file sequences by",
					"a third party encoder program.",
					"   ",
					"Suitable encoder program must be installed before movie clips can be created",
					"   ",
					"Select Help->Codec Info for more information."};
		DialogUtils.showTwoTextStyleDialog( JOptionPane.WARNING_MESSAGE, null, buttons, bLines, tLines );
	}

}//end class
